﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace SmartRoute.Data
{
    [Serializable]
    
    public class DynamicRouteCache: IRoutingService//, IDeserializationCallback
    {
        //constant string for error message
        private const string incorrectDeserializationErrorString =
            "DynamicRouteCache: Constructed (most likely deserialized) without providing a pointer to a current routing service.";
        
        //A simple request that can be stored as a key. In the future (.NET 4.0) use a tuple instead.
        [Serializable]
        private struct Request: IEquatable<Request>
        {
            public readonly Location From;
            public readonly Location To;
            public Request(Location from, Location to)
            {
                From = from;
                To = to;
            }

            #region IEquatable<Request> Members
            /// <summary>
            /// overrides equals function
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                return obj is Request && ((Request)obj).Equals(this);
            }
            /// <summary>
            /// checks if the starting and ending locations are equal to a location in the cashe
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            public bool Equals(Request other)
            {
                return From == other.From && To == other.To;
            }

            public override int GetHashCode()
            {
                //For now, this is non-commutative and good enough.
                //Better hash function later.
                unchecked { return From.GetHashCode() << 1 + To.GetHashCode(); }
            }

            #endregion

        }

        private Dictionary<Request, Route> cachedRoutes = new Dictionary<Request,Route>();

        //Defaults to a routing service that only produces errors.
        [NonSerialized]
        private IRoutingService originalRoutingService = new ErrorRoutingService(incorrectDeserializationErrorString);

        public IRoutingService RealRoutingService
        {
            get { return originalRoutingService; }
            set { originalRoutingService = value; }
        }

        public void rebuildCache()
        {
            foreach (var kvp in this.cachedRoutes) cachedRoutes[kvp.Key] = originalRoutingService.FromTo(kvp.Value.Source, kvp.Value.Destination);
        }

        /// <summary>
        /// Returns a new cached routing service
        /// </summary>
        /// <param name="toCache">The routing service whose results are to be cached.</param>
        public DynamicRouteCache(IRoutingService toCache)
        {
            originalRoutingService = toCache;
        }

        protected DynamicRouteCache() { }
        /// <summary>
        /// Call this method instead of deserializing as the reference to the routing service is not serialized.
        /// </summary>
        /// <param name="toCache">The reference to the routing service which is to be cached.</param>
        /// <param name="file">The file in which the DynamicRouteCache is stored.</param>
        /// <returns>A deserialized DynamicRouteCache with a working reference.</returns>
        public static DynamicRouteCache FromFile(IRoutingService toCache, System.IO.FileStream file)
        {
            BinaryFormatter bf = new BinaryFormatter();
            DynamicRouteCache output = (DynamicRouteCache)(bf.Deserialize(file));
            output.originalRoutingService = toCache;
            foreach (var route in output.cachedRoutes.Values) route.RoutingService = output;
            return output;
        }

        #region IRoutingService Members

        public Route FromTo(Location source, Location destination)
        {
            var request = new Request(source, destination);
            if (!cachedRoutes.ContainsKey(request))
            {
                var newRoute = originalRoutingService.FromTo(source, destination);
                newRoute.RoutingService = this;
                cachedRoutes.Add(request, newRoute);
            }
            else
            {
                if (cachedRoutes[request].RoutingService != this)
                    cachedRoutes[request].RoutingService = this;
            }
            return cachedRoutes[request];
        }

        #endregion

        #region IDeserializationCallback Members

        //public void OnDeserialization(object sender)
        //{
        //    foreach (var kvp in this.cachedRoutes)
        //        kvp.Value.RoutingService = this;
        //}

        #endregion
    }
}
